package web

import (
	"crypto/tls"
	"fmt"
	"net"
	"net/http"
	"net/smtp"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// Server represents a web.go server.
type Server struct {
	CookieSecret string
	routes       []route
	//save the listener so it can be closed
	l      net.Listener
	SendTo string
}

func NewServer() *Server {
	return &Server{}
}

type Handler func(ctx *Context)

type route struct {
	r       string
	cr      *regexp.Regexp
	method  string
	handler Handler
}

func (s *Server) addRoute(r string, method string, handler Handler) {
	cr, err := regexp.Compile(r)
	if err != nil {
		s.SendMail(fmt.Sprintln("Error in route regex:", r))
		return
	}

	s.routes = append(s.routes, route{r, cr, method, handler})
}

// ServeHTTP is the interface method for Go's http server package
func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
	s.Process(c, req)
}

// Process invokes the routing system for server s
func (s *Server) Process(c http.ResponseWriter, req *http.Request) {
	s.routeHandler(req, c)
}

// Get adds a handler for the 'GET' http method for server s.
func (s *Server) Get(route string, handler Handler) {
	s.addRoute(route, "GET", handler)
}

// Post adds a handler for the 'POST' http method for server s.
func (s *Server) Post(route string, handler Handler) {
	s.addRoute(route, "POST", handler)
}

// Put adds a handler for the 'PUT' http method for server s.
func (s *Server) Put(route string, handler Handler) {
	s.addRoute(route, "PUT", handler)
}

// Delete adds a handler for the 'DELETE' http method for server s.
func (s *Server) Delete(route string, handler Handler) {
	s.addRoute(route, "DELETE", handler)
}

// Match adds a handler for an arbitrary http method for server s.
func (s *Server) Match(method string, route string, handler Handler) {
	s.addRoute(route, method, handler)
}

// Run starts the web application and serves HTTP requests for s
func (s *Server) Run(addr string) {
	mux := http.NewServeMux()

	mux.Handle("/css/", http.FileServer(http.Dir("static")))
	mux.Handle("/images/", http.FileServer(http.Dir("static")))
	mux.Handle("/js/", http.FileServer(http.Dir("static")))
	mux.Handle("/files/", http.FileServer(http.Dir("download")))
	mux.Handle("/", s)

	for {
		fmt.Fprintf(os.Stdout, "%s web.go serving %s\n", time.Now().Format("2006-01-02 15:04:05.000"), addr)
		l, err := net.Listen("tcp", addr)
		if err != nil {
			s.SendMail(err.Error())
		}
		s.l = l
		err = http.Serve(s.l, mux)
		if err != nil {
			s.SendMail(err.Error())
		}
	}
}

// RunTLS starts the web application and serves HTTPS requests for s.
func (s *Server) RunTLS(addr string, config *tls.Config) error {
	mux := http.NewServeMux()
	mux.Handle("/", s)
	l, err := tls.Listen("tcp", addr, config)
	if err != nil {
		fmt.Fprintf(os.Stdout, "%s Listen: %v\n", time.Now().Format("2006-01-02 15:04:05.000"), err)
		return err
	}

	s.l = l
	return http.Serve(s.l, mux)
}

// Close stops server s.
func (s *Server) Close() {
	if s.l != nil {
		s.l.Close()
	}
}

// the main route handler in web.go
func (s *Server) routeHandler(req *http.Request, w http.ResponseWriter) {
	if req.Method == "GET" && req.URL.Path == "/favicon.ico" {
		return
	}

	requestPath := req.URL.Path
	ctx := Context{req, map[string]string{}, s, w}

	//ignore errors from ParseForm because it's usually harmless.
	req.ParseForm()
	if len(req.Form) > 0 {
		for k, v := range req.Form {
			ctx.Params[k] = v[0]
		}
		fmt.Fprintf(os.Stdout, "%s %-22s%-6s%-16s%s\n", time.Now().Format("2006-01-02 15:04:05.000"), req.RemoteAddr, req.Method, requestPath, ctx.Params)
	} else {
		fmt.Fprintf(os.Stdout, "%s %-22s%-6s%-16s\n", time.Now().Format("2006-01-02 15:04:05.000"), req.RemoteAddr, req.Method, requestPath)
	}

	//set some default headers
	ctx.SetHeader("Server", "web.go", true)
	tm := time.Now().UTC()
	ctx.SetHeader("Date", webTime(tm), true)

	//Set the default content-type
	ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)

	for _, route := range s.routes {
		cr := route.cr
		//if the methods don't match, skip this handler (except HEAD can be used in place of GET)
		if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
			continue
		}

		if !cr.MatchString(requestPath) {
			continue
		}
		match := cr.FindStringSubmatch(requestPath)

		if len(match[0]) != len(requestPath) {
			continue
		}

		route.handler(&ctx)

		return
	}

	notfound :=
		`
			<p><b><font size="5" color="red">Page Not Found</font></b></p>
	    	<p>What you were looking for is just not there.
	    	<p><a href="javascript:history.back(-1)">Return</a>
		`
	ctx.NotFound(notfound)
}

func (s *Server) SendMail(messages string) {
	user := "zhujianhua@rosenbergerap.com"
	host := "mailserver.rosenbergerap.com:25"
	to := s.SendTo

	t := time.Now()
	timestr := t.Format("20060102150405")
	unixs := strconv.FormatInt(t.UnixNano(), 16)
	filename := filepath.Join("log", timestr+"_"+unixs+".log")
	subject := "SFCWebServer.exe occurred Error"
	messages = "error file: " + filename + "\r\n" + messages
	msg := []byte("To: " + to + "\r\nFrom: " + user + "\r\nSubject: " + subject + "\r\n\r\n" + messages)
	smtp.SendMail(host, nil, user, strings.Split(to, ";"), msg)

	f, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	f.WriteString(messages)
}
